package image.lines;

import image.processing.ImageProcessor;
import image.threshold.OtsuThresholderA;
import image.utils.ImageUtilitiesImpl;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class HoughCircles {
	private double aMin;
	private double aMax;
	private double bMin;
	private double bMax;
	private double rMin;
	private double rMax;
	private BufferedImage src;
	private int acumulator[][][];
	private double[] discretizatedA;
	private double[] discretizatedB;
	private double[] discretizatedRadious;
	private float epsilon;
	private int percent;

	public HoughCircles(int discretizationCenterA, int discretizationCenterB,
			int discretizationRadious, float epsilon, BufferedImage src,
			int percent) {

		aMin = 0;
		aMax = src.getWidth() - 1;
		bMin = 0;
		bMax = src.getHeight() - 1;

		OtsuThresholderA otsu = new OtsuThresholderA();
		this.src = ImageProcessor.threshold(src, otsu.doThreshold(src));
		this.epsilon = epsilon;
		this.percent = percent;

		this.discretizatedA = new double[discretizationCenterA];
		this.discretizatedB = new double[discretizationCenterB];
		this.discretizatedRadious = new double[discretizationRadious];

		this.acumulator = new int[discretizationCenterA][discretizationCenterB][discretizationRadious];
		double ant = this.aMin;
		int xDim = src.getHeight();
		for (int i = 0; i < this.discretizatedA.length; i++) {
			this.discretizatedA[i] = ant;
			ant += ((double) xDim) / (discretizationCenterA - 1);
		}
		ant = this.bMin;
		int yDim = src.getWidth();
		for (int i = 0; i < this.discretizatedB.length; i++) {
			this.discretizatedB[i] = ant;
			ant += ((double) yDim) / (discretizationCenterB - 1);
		}
		ant = 0;
		double maxR = ((double) Math.max(src.getHeight(), src.getWidth()) / 2);

		for (int i = 0; i < this.discretizatedRadious.length; i++) {
			this.discretizatedRadious[i] = ant;
			ant += maxR / (discretizationRadious - 1);
		}

		printArray(discretizatedA);
		printArray(discretizatedB);
		printArray(discretizatedRadious);

	}

	private void printArray(double[] array) {
		System.out.print("[ ");
		for (double i : array) {
			System.out.print(i + " ");
		}
		System.out.println("]");
	}

	public BufferedImage findCircles() {
		WritableRaster raster = src.getRaster();
		/*
		 * System.out.println("rho:"); printArray(this.discretizatedRho);
		 * System.out.println("theta:"); printArray(this.discretizatedTheta);
		 */
		for (int x = 0; x < src.getWidth(); x++) {
			for (int y = 0; y < src.getHeight(); y++) {
				int sample = raster.getSample(x, y, 0);
				if (sample == 255) { // cambiarrrrrrrrrr
					satisfiesCircleEquation(x, y);
				}
			}
		}
		Set<int[]> maximos = getLocalMaximuns(this.percent); //
		for (int[] maximo : maximos) {
			System.out.println("a: " + maximo[0] + " b " + maximo[1] + "c: "
					+ maximo[2] + " acum: "
					+ acumulator[maximo[0]][maximo[1]][maximo[2]]);
			System.out.println("a: " + discretizatedA[maximo[0]] + "b: "
					+ discretizatedB[maximo[1]] + "r: "
					+ discretizatedRadious[maximo[2]]);

		}
		return drawLines3(maximos);

	}

	private void satisfiesCircleEquation(int x, int y) {
		double xcenter;
		double ycenter;
		double radious;
		double value;
		for (int a = 0; a < discretizatedA.length; a++) {
			for (int b = 0; b < discretizatedB.length; b++) {
				for (int r = 0; r < discretizatedRadious.length; r++) {
					xcenter = (x - discretizatedA[a]) * (x - discretizatedA[a]);
					ycenter = (y - discretizatedB[b]) * (y - discretizatedB[b]);
					radious = discretizatedRadious[r] * discretizatedRadious[r];
					value = Math.abs(xcenter + ycenter - radious);
					if (value < epsilon) {
						acumulator[a][b][r] += 1;
					}
				}
			}
		}
	}

	private int[] getMax() {
		int max = Integer.MIN_VALUE;
		int[] resp = new int[3];
		for (int a = 0; a < acumulator.length; a++) {
			for (int b = 0; b < acumulator[a].length; b++) {
				for (int r = 0; r < acumulator[a][b].length; r++) {
					if (acumulator[a][b][r] > max) {
						max = acumulator[a][b][r];
						resp[0] = a;
						resp[1] = b;
						resp[2] = r;
					}
				}
			}
		}
		return resp;
	}

	private Set<int[]> findMaxLocations() {
		int[] maxLocation = getMax();
		int max = this.acumulator[maxLocation[0]][maxLocation[1]][maxLocation[2]];
		Set<int[]> resp = new HashSet<int[]>();
		for (int a = 0; a < acumulator.length; a++) {
			for (int b = 0; b < acumulator[a].length; b++) {
				for (int r = 0; r < acumulator[a][b].length; r++) {
					if (acumulator[a][b][r] == max) {
						max = acumulator[a][b][r];
						int[] values = new int[3];
						values[0] = a;
						values[1] = b;
						values[2] = r;
						resp.add(values);
					}
				}
			}
		}
		return resp;
	}

	/*
	 * finds maximuns percent minor than the maximun value of the acumulator;
	 * percent 0-100
	 */
	private Set<int[]> getLocalMaximuns(int percent) {
		Set<int[]> resp = findMaxLocations();
		int[] maxLocation = resp.iterator().next();
		int max = this.acumulator[maxLocation[0]][maxLocation[1]][maxLocation[2]];
		System.out.println("max: " + max);
		if (percent == 0) {
			return resp;
		}
		double tolerance = max - ((double) max * percent) / 100;
		System.out.println("tole : " + tolerance);
		for (int a = 0; a < acumulator.length; a++) {
			for (int b = 0; b < acumulator[a].length; b++) {
				for (int r = 0; r < acumulator[a][b].length; r++) {
					if (acumulator[a][b][r] > Math.ceil(tolerance)) {
						max = acumulator[a][b][r];
						int[] values = new int[3];
						values[0] = a;
						values[1] = b;
						values[2] = r;
						resp.add(values);
					}
				}
			}
		}
		return resp;
	}

	private BufferedImage drawLines2(Set<int[]> maximos) {

		BufferedImage resp = ImageUtilitiesImpl.getInstance().toRGB(src);
		WritableRaster raster = resp.getRaster();
		for (int[] maximo : maximos) {
			int[][] aux = getCirclePoints(maximo);
			for (int i = 0; i < aux[0].length; i++) {
				// System.out.println(aux[0][i]+","+aux[1][i]);
				raster.setSample(aux[0][i], aux[1][i], 0, 0);
				raster.setSample(aux[0][i], aux[1][i], 1, 0);
				raster.setSample(aux[0][i], aux[1][i], 2, 255);
			}
		}
		return resp;
	}

	private BufferedImage drawLines3(Set<int[]> maximos) {

		BufferedImage resp = ImageUtilitiesImpl.getInstance().toRGB(src);
		for (int[] maximo : maximos) {
			Graphics2D g2 = (Graphics2D) resp.getGraphics();
			int xstart = (int) Math.ceil(discretizatedA[maximo[0]]
					- discretizatedRadious[maximo[2]]);
			int xend = (int) Math.ceil(discretizatedA[maximo[0]]
					+ discretizatedRadious[maximo[2]]);
			int ystart = (int) Math.ceil(discretizatedB[maximo[1]]
					- discretizatedRadious[maximo[2]]);
			int yend = (int) Math.ceil(discretizatedB[maximo[1]]
					+ discretizatedRadious[maximo[2]]);
			System.out.println("xstart" + xstart);
			System.out.println("ystart" + ystart);
			Ellipse2D e = new Ellipse2D.Double(xstart, ystart, Math.abs(xstart
					- xend), Math.abs(ystart - yend));
			g2.setPaint(Color.GREEN);
			g2.setStroke(new BasicStroke(1));
			g2.draw(e);
		}

		return resp;
	}

	private int[][] getCirclePoints(int[] maximo) {
		List<Integer> xs = new ArrayList<Integer>();
		List<Integer> ys = new ArrayList<Integer>();
		double xcenter;
		double ycenter;
		double radious;
		double value;
		double ERROR = 1d;

		for (int x = 0; x < src.getWidth(); x++) {
			for (int y = 0; y < src.getHeight(); y++) {
				xcenter = (x - discretizatedA[maximo[0]])
						* (x - discretizatedA[maximo[0]]);
				ycenter = (y - discretizatedB[maximo[1]])
						* (y - discretizatedB[maximo[1]]);
				radious = discretizatedRadious[maximo[2]]
						* discretizatedRadious[maximo[2]];
				value = Math.abs(xcenter + ycenter - radious);
				if (value < ERROR) {
					xs.add(x);
					ys.add(y);
				}
			}
		}
		int[][] resp = new int[2][xs.size()];
		for (int i = 0; i < xs.size(); i++) {
			resp[0][i] = xs.get(i);
			resp[1][i] = ys.get(i);
		}
		return resp;
	}

}
